#include <strings.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "Assembler.h"
#include "ConstantPool.h"


#define checkForValidOpcode(op) (assert( opcodes[op] ))
#define checkForValidNumberOfOperands(op,n_ops) \
  (assert( opcodes[op] && opcodes[op]->n_operands==(n_ops) ))

enum {
  OPF_byte=0x1,
  OPF_word=0x2,
  OPF_index=0x4,
  OPF_offset=0x8,
};

static inline bool isByte(u1 opflags) { return opflags&OPF_byte; };
static inline bool isWord(u1 opflags) { return opflags&OPF_word; };
static inline bool isIndex(u1 opflags) { return opflags&OPF_index; };
static inline bool isOffset(u1 opflags) { return opflags&OPF_offset; };

enum {
  MAX_OPERANDS=2
};

struct OpcodeDescr {
  unsigned char code;
  unsigned char n_operands;
  const char *  label;
  unsigned char op_flags[MAX_OPERANDS];
};

static bool          opcodes_initialized=false;
static OpcodeDescr * opcodes[256];

OpcodeDescr static_opcodes[]={
  {   0, 0, "nop" },
  {   3, 0, "iconst_0" },
  {   4, 0, "iconst_1" },
  {   5, 0, "iconst_2" },
  {   6, 0, "iconst_3" },
  {   7, 0, "iconst_4" },
  {   8, 0, "iconst_5" },

  {  14, 0, "dconst_0" },
  {  15, 0, "dconst_1" },

  {  17, 1, "sipush", { OPF_word }},
  {  18, 1, "ldc1", { OPF_byte|OPF_index }},
  {  19, 1, "ldc2", { OPF_word|OPF_index }},

  {  20, 1, "ldc2w", { OPF_word|OPF_index }},

  {  21, 1, "iload" },
  {  24, 1, "dload",  { OPF_byte }},
  {  26, 0, "iload_0" },
  {  27, 0, "iload_1" },
  {  28, 0, "iload_2" },
  {  29, 0, "iload_3" },

  {  38, 0, "dload_0" },
  {  39, 0, "dload_1" },
  {  40, 0, "dload_2" },
  {  41, 0, "dload_3" },

  {  42, 0, "aload_0" },
  {  43, 0, "aload_1" },
  {  44, 0, "aload_2" },
  {  45, 0, "aload_3" },


  {  57, 1, "dstore" , { OPF_byte }},

  {  59, 0, "istore_0" },
  {  60, 0, "istore_1" },
  {  61, 0, "istore_2" },
  {  62, 0, "istore_3" },

  {  71, 0, "dstore_0" },
  {  72, 0, "dstore_1" },
  {  73, 0, "dstore_2" },
  {  74, 0, "dstore_3" },


  {  96, 0, "iadd" },
  {  97, 0, "ladd" },
  {  98, 0, "fadd" },
  {  99, 0, "dadd" },

  { 100, 0, "isub" },
  { 101, 0, "lsub" },
  { 102, 0, "fsub" },
  { 103, 0, "dsub" },

  { 107, 0, "dmul" },

  { 111, 0, "ddiv" },

  { 151, 0, "dcmpl" },
  { 152, 0, "dcmpg" },
  { 153, 1, "ifeq", { OPF_word|OPF_offset } },
  { 154, 1, "ifne", { OPF_word|OPF_offset } },
  { 155, 1, "iflt", { OPF_word|OPF_offset } },
  { 156, 1, "ifge", { OPF_word|OPF_offset } },
  { 157, 1, "ifgt", { OPF_word|OPF_offset } },
  { 158, 1, "ifle", { OPF_word|OPF_offset } },

  { 153, 1, "ifeq", { OPF_word|OPF_offset } },
  { 158, 1, "ifle", { OPF_word|OPF_offset } },

  { 167, 1, "goto", { OPF_word|OPF_offset } },

  { 177, 0, "return" },
  { 178, 1, "getstatic" , { OPF_word|OPF_index } },
  { 179, 1, "putstatic" , { OPF_word|OPF_index } },

  { 182, 1, "invokevirtual",  { OPF_word|OPF_index} },
  { 183, 1, "invokenonvirtual", { OPF_word|OPF_index}  },

  { 198, 1, "ifnull", { OPF_word|OPF_offset } },
  { 199, 1, "ifnotnull", { OPF_word|OPF_offset } },
};

static void init_opcodes_table()
{  
  if(!opcodes_initialized) {
    int i;
    int n=sizeof(static_opcodes)/sizeof(OpcodeDescr);
    bzero(opcodes,sizeof(opcodes));
    OpcodeDescr * op;
    for(i=0;i<n;i++) {
      op=&static_opcodes[i];
      opcodes[op->code]=op;
    }
    opcodes_initialized=true;
  }
}
 

Assembler::Assembler()
{
  init_opcodes_table();

  allocated_code=10*1024;
  first_byte_free=0;
  code=(u1 *)calloc(1,allocated_code);
  //code[0]=OPC_return;
}

void Assembler::dumpOpcodes()
{
  for(int i=0;i<256;i++) {
    if(opcodes[i]) {
      printf("Opcode: '%-14s', code: %d (%02X), n operands: %d\n",
          opcodes[i]->label,opcodes[i]->code,opcodes[i]->code,opcodes[i]->n_operands);
    }
  }
}

void  Assembler::addByte(unsigned char b)
{
  assert(first_byte_free<allocated_code);
  code[first_byte_free++]=b;
}

u4 Assembler::getAP()
{
  return first_byte_free;
}

static void dumpOpByte(u2 currentAddress, u1 op, u2 flags, ConstantPool * cp)
{
  if(isIndex(flags)) {
    printf(" <%d> ",op); 
    cp->getAt(op)->dump(cp);
  } else {
    printf(" %02x flags: %d",op,flags);
  }
}

static void dumpOpWord(u2 currentAddress,u2 op, u2 flags, ConstantPool * cp)
{
  if(isIndex(flags)) {
    printf(" <%d> ",op); cp->getAt(op)->dump(cp);
  } else if(isOffset(flags)) {
    printf(" %04X (offset: %X)",currentAddress+op,op);
  } else {
    printf(" %02x ",op);
  }
}

void Assembler::disassemble(ConstantPool * cp)
{
  u1 c;
  u1 cb;
  u2 cw;
  u2 currentAddress;
  for(u4 i=0;i<first_byte_free;/*i++*/) {
    c=code[currentAddress=i++];
    if(opcodes[c]) {
      printf("    [%04X] %-14s",currentAddress,opcodes[c]->label);
      for(int j=0;j<opcodes[c]->n_operands && i<first_byte_free;j++) {
        if(isByte(opcodes[c]->op_flags[j])) {
          cb=code[i++];
          dumpOpByte(currentAddress,cb,opcodes[c]->op_flags[j],cp);
        } else {
          cw=code[i++];
          cw=(cw<<8)|code[i++];
          dumpOpWord(currentAddress,cw,opcodes[c]->op_flags[j],cp);
        }
        //printf(" %02X",code[i]);
      }
      printf("\n");
    } else {
      printf("    [%04X] ??? (%03d)\n",i,c);
    }
  }
}

void Assembler::save(FILE *f)
{
  writeUint32(f,first_byte_free);
  fwrite(code,first_byte_free,1,f);
}

Assembler::Assembler(FILE *f)
{
  init_opcodes_table();

  u4 code_length=readUint32(f);

  code=new u1[code_length];
  allocated_code=code_length;
  first_byte_free=code_length;

  fread(code,code_length,1,f);
}

void Assembler::generateEnum()
{
  printf("enum {\n");
  for(int i=0;i<256;i++) {
    if(opcodes[i]) {
      printf("OPC_%s=0x%02X,\n",
          opcodes[i]->label,opcodes[i]->code);
    }
  }
  printf("};\n");
}

u4 Assembler::getSize()
{
  return first_byte_free;
}

void Assembler::resetCode()
{
  first_byte_free=1;
  code[0]=OPC_return;
}

void Assembler::assemble(u1 opcode)
{
  checkForValidOpcode(opcode);
  checkForValidNumberOfOperands(opcode,0);
  addByte(opcode);
}

void Assembler::relocAt(u4 offset,u4 targetAddress)
{
  assert(offset<first_byte_free);
  assert(targetAddress>offset);

  u2 value=targetAddress-offset;
  code[offset+1]=(u1)(value>>8);
  code[offset+2]=(u1)(value&0xff);
}

void Assembler::assembleAt(u4 offset, u1 opcode, u4 operand)
{
  assert(offset<first_byte_free);

  checkForValidOpcode(opcode);
  checkForValidNumberOfOperands(opcode,1);

  OpcodeDescr * odescr=opcodes[opcode];

  code[offset]=opcode;

  if(isByte(odescr->op_flags[0])) {
    code[offset+1]=(u1)(operand&0xff);
  } else {
    assert(isWord(odescr->op_flags[0]));
    code[offset+1]=(u1)(operand>>8);
    code[offset+2]=(u1)(operand&0xff);
  }
}

void Assembler::assemble(u1 opcode, u4 operand)
{
  checkForValidOpcode(opcode);
  checkForValidNumberOfOperands(opcode,1);

  OpcodeDescr * odescr=opcodes[opcode];

  addByte(opcode);

  if(isByte(odescr->op_flags[0])) 
    addByte((u1)(operand&0xff));
  else {
    assert(isWord(odescr->op_flags[0]));
    addByte( (u1)(operand>>8));
    addByte( (u1)(operand&0xff));
  }
}

